﻿using System;
using System.Runtime.InteropServices;

namespace NanoMvvm
{
    public class WeakReference<T> : IWeakReference<T>, IEquatable<WeakReference<T>>, IEquatable<T>, IDisposable where T : class
    {
        private readonly int hashCode;

        public WeakReference(T target)
        {
            if (target == null)
                throw new ArgumentNullException("target");
            hashCode = target.GetHashCode();
            InitializeHandle(target);
        }

        protected void InitializeHandle(T target)
        {
            Handle = GCHandle.Alloc(target, GCHandleType.Weak);
        }

        ~WeakReference()
        {
            Dispose();
        }

        public void Dispose()
        {
            Handle.Free();
            GC.SuppressFinalize(this);
        }

        public virtual bool IsTargetAlive
        {
            get { return (Handle.Target != null); }
        }

        public virtual T Target
        {
            get
            {
                object o = Handle.Target;

                if ((ReferenceEquals(o, null)) || (!(o is T)))
                    return null;

                return o as T;
            }
        }

        protected GCHandle Handle { get; private set; }

        public override int GetHashCode()
        {
            return hashCode;
        }

        public override bool Equals(object other)
        {
            if (ReferenceEquals(null, other)) return false;

            var wr = other as WeakReference<T>;
            if (!ReferenceEquals(null, wr))
            {
                return Equals(wr);
            }

            var t = other as T;
            if(!ReferenceEquals(null, t))
            {
                return Equals(t);
            }

            return false;
        }

        public bool Equals(T other)
        {
            var t = Target;
            return !ReferenceEquals(t, null) && t.Equals(other);
        }

        public bool Equals(WeakReference<T> other)
        {
            return ReferenceEquals(other.Target, Target);
        }
    }
}